package eirkdbd.lab1.fuzzyqueries;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import eirkdbd.lab1.data.Team;
import eirkdbd.lab1.fuzzyqueries.circumstances.Circumstance;
import eirkdbd.lab1.membership.MembershipFunction;
import eirkdbd.lab1.membership.functions.helpers.FuzzyOperations;
import eirkdbd.lab1.membership.functions.helpers.norms.implications.Implication;
import eirkdbd.lab1.membership.functions.helpers.norms.tnorms.Tnorm;

/**
 * Represents Bipolar Query for Team Data<br>
 * Constructs bipolar query from Mandatory Membership Function (C), Optional Membership Function (P) 
 * and List of Teams to execute query on. <br>
 * Provides execute function, which triggers query and returns results stored in sorted (by membership degrees) 
 * map with following syntax key : team , value : membership degree
 */
public class BipolarQuery {
	
	private MembershipFunction C;
	private MembershipFunction P;
	private Circumstance W = null;
	private List<Team> teams;
	private Tnorm tnorm;
	private Implication implication;
	/**
	 * Creates BipolarQuery object 
	 * @param C - Mandatory condition
	 * @param P - Possibly condition
	 * @param teams - List of teams to query them
	 * @param tnorm - Tnorm to use for computation
	 */
	public BipolarQuery(MembershipFunction C, MembershipFunction P, List<Team> teams, Tnorm tnorm, Implication implication){
		if (C.equals(P))
			throw new IllegalArgumentException("C and P criterium can't be equal");
		this.C = C;
		this.P = P;
		this.teams = teams;
		this.tnorm = tnorm;
		this.implication = implication;
	}
	
	/**
	 * Creates Circumstantial BipolarQuery object 
	 * @param C - Mandatory condition
	 * @param P - Possibly condition
	 * @param W - Circumstances 
	 * @param teams - List of teams to query them
	 * @param tnorm - Tnorm to use for computation
	 */
	public BipolarQuery(MembershipFunction C, MembershipFunction P, Circumstance W, List<Team> teams, Tnorm tnorm, Implication implication){
		if (C.equals(P))
			throw new IllegalArgumentException("C and P criterium can't be equal");
		this.C = C;
		this.P = P;
		this.W = W;
		this.teams = teams;
		this.tnorm = tnorm;
		this.implication = implication;
	}
	
	/**
	 * Executes bipolar query and return appropriate results structure
	 * 
	 * @return results stored in sorted (by membership degrees) 
	 * map with following syntax key : team , value : membership degree
	 */
	public Map<Team, Double> execute(){
		System.out.println(W == null);
		Map<Team, Double> bipolarQueryResults = new HashMap<Team, Double>();
		//BIPOLAR QUERY EQUATION - LEGEND:
		// q(C, P, t, T) = tnorm(C(t), max(1- max min(C(s), P(s), P(t))), s inside T
		// max(min (C(s), P(s))) <-- setMax  for bipolar query
		// max(min ( W(s, t),  (tnorm C(s) , P(s)) ) ) 
		// setMax -> P(t) <-- rightSide
		double setMax = countSetMax();
		double rightSide;
		double membershipDegree;
		
		for (Team team : teams){
			if (W == null)
				rightSide = implication.value(setMax, P.getValue(team));
			else
				rightSide = implication.value(countSetMax(team), P.getValue(team));
			membershipDegree = tnorm.value(C.getValue(team), rightSide);
			bipolarQueryResults.put(team, membershipDegree);
		}
		
		return sortResults(bipolarQueryResults);
	}

	private double countSetMax(Team currentTeam) {
		List<Double> minList = new ArrayList<Double>();		
		for (Team team : teams){
			minList.add(FuzzyOperations.min(W.getValue(team, currentTeam)  ,FuzzyOperations.min(C.getValue(team), P.getValue(team) )  ));
		}		
		return  FuzzyOperations.max(minList);
	}

	/**
	 * Sorts bipolar query results
	 * @param bipolarQueryResults - hashMap representing bipolar query results
	 * @return  - sorted (dec) by membership degree  map(linked hash map) of bipolar query results
	 */
	private Map<Team, Double> sortResults(Map<Team, Double> bipolarQueryResults) {
		
		List<Entry<Team, Double>> list = createSortedEntryList(bipolarQueryResults);
		
		Map<Team, Double> sortedMap = createSortedMapFromEntryList(list);
		
		return sortedMap;
	}

	/**
	 * Create sorted entry list from map
	 * @param bipolarQueryResults
	 * @return
	 */
	private List<Entry<Team, Double>> createSortedEntryList(Map<Team, Double> bipolarQueryResults) {
		//create entry list
		List<Entry<Team, Double>> list = new LinkedList<Entry<Team, Double>>(bipolarQueryResults.entrySet());
		 
		// sort list based on comparator
		Collections.sort(list, new Comparator<Entry<Team, Double>>() {
			public int compare(Entry<Team, Double> arg0, Entry<Team, Double> arg1) {				
				return arg1.getValue().compareTo(arg0.getValue());
			}			
		});
		
		return list;
	}

	/**
	 * Creates sorted map from sorted entry list
	 * @param list
	 * @return
	 */
	private Map<Team, Double> createSortedMapFromEntryList(List<Entry<Team, Double>> list) {
		//create new map
		Map<Team, Double> sortedMap = new LinkedHashMap<Team, Double>();
		for (Iterator<Entry<Team, Double>> it = list.iterator(); it.hasNext();) {
			Map.Entry<Team, Double> entry = (Map.Entry<Team, Double>) it.next();
			sortedMap.put(entry.getKey(), entry.getValue());
		}
		return sortedMap;
	}

	/**
	 * Counts setMax bipolar query equation part
	 */
	private double countSetMax() {
		List<Double> minList = new ArrayList<Double>();		
		for (Team team : teams){
			minList.add(tnorm.value(C.getValue(team), P.getValue(team)));
		}		
		return  FuzzyOperations.max(minList);
	}
}
